package l2

import (
	"fmt"
	"zstackgo/pkg/core/cascade"
	"zstackgo/pkg/core/cloudbus"
	"zstackgo/pkg/core/componentloader"
	"zstackgo/pkg/core/db"
	"zstackgo/pkg/core/errorcode"
	hcore "zstackgo/pkg/header/core"
	herrorcode "zstackgo/pkg/header/errorcode"
	"zstackgo/pkg/header/identity"
	"zstackgo/pkg/header/message"
	hl2 "zstackgo/pkg/header/network/l2"
	"zstackgo/pkg/header/zone"
	"zstackgo/spring"

	"github.com/meilihao/golib/v2/log"
	"github.com/meilihao/golib/v2/misc"
	"go.uber.org/zap"
)

func init() {
	o := new(L2NetworkCascadeExtension)
	spring.Register(o, "L2NetworkCascadeExtension", []*spring.PluginExtension{
		{
			Interface: "core.cascade.CascadeExtensionPoint",
		},
	})
}

const (
	Name = "L2NetworkVO"
)

type L2NetworkCascadeExtension struct {
	dbf         db.DatabaseFacade               `autowire:"DatabaseFacadeImpl"`
	extpEmitter *L2NetworkExtensionPointEmitter `autowire:"L2NetworkExtensionPointEmitter"`
	bus         cloudbus.CloudBus               `autowire:"CloudBusImpl3"`
	errf        errorcode.ErrorFacade           `autowire:"ErrorFacadeImpl"`
	pluginRgty  componentloader.PluginRegistry  `autowire:"PluginRegistryImpl"`
}

func (ce *L2NetworkCascadeExtension) AsyncCascade(action *cascade.CascadeAction, completion hcore.Completion) {
	if action.ActionCode == cascade.DELETION_CHECK_CODE {
		ce.HandleDeletionCheck(action, completion)
	} else if misc.IsInStrings(action.ActionCode, []string{cascade.DELETION_DELETE_CODE, cascade.DELETION_FORCE_DELETE_CODE}) {
		ce.HandleDeletion(action, completion)
	} else if action.ActionCode == cascade.DELETION_CLEANUP_CODE {
		ce.HandleDeletionCleanup(action, completion)
	} else if action.ActionCode == hl2.DETACH_L2NETWORK_CODE {
		ce.HandleDetach(action, completion)
	} else {
		completion.Success()
	}
}

func (ce *L2NetworkCascadeExtension) HandleDetach(action *cascade.CascadeAction, completion hcore.Completion) {
	structs := action.ParentIssuerContext.([]*hl2.L2NetworkDetachStruct)

	msgs := make([]message.NeedReplyMessage, 0, len(structs))
	for _, s := range structs {
		msg := &hl2.DetachL2NetworkFromClusterMsg{
			ClusterUuid:   s.ClusterUuid,
			L2NetworkUuid: s.L2NetworkUuid,
		}

		ce.bus.MakeTargetServiceIdByResourceUuidMsg(msg, hl2.SERVICE_ID, s.L2NetworkUuid)

		msgs = append(msgs, msg)
	}

	ce.bus.SendNeedRepliesWithParallel(msgs, len(msgs), &CloudBusListCallBackHandleDetach{
		Completion: completion,
	})
}

func (ce *L2NetworkCascadeExtension) HandleDeletionCleanup(action *cascade.CascadeAction, completion hcore.Completion) {
	l2invs, err := ce.l2NetworkFromAction(action)
	if err == nil {
		if len(l2invs) > 0 {
			for _, l := range l2invs {
				ce.dbf.GetEntityManager().Where("Uuid = ?", l.Uuid).Delete(hl2.ModelL2NetworkVO)
			}
		} else {
			ce.dbf.GetEntityManager().Delete(hl2.ModelL2NetworkVO)
		}
	} else {
		log.Glog.Warn("HandleDeletionCleanup", zap.Error(err))
		ce.dbf.GetEntityManager().Delete(hl2.ModelL2NetworkVO)
	}

	completion.Success()
}

func (ce *L2NetworkCascadeExtension) HandleDeletion(action *cascade.CascadeAction, completion hcore.Completion) {
	l2invs, _ := ce.l2NetworkFromAction(action)
	if len(l2invs) == 0 {
		completion.Success()
		return
	}

	if len(l2invs) != 0 {
		exts := ce.pluginRgty.GetExtensionList("L2NetworkCascadeFilterExtensionPoint")
		rexts := make([]L2NetworkCascadeFilterExtensionPoint, 0, len(exts))
		for _, v := range exts {
			i := v.(L2NetworkCascadeFilterExtensionPoint)
			rexts = append(rexts, i)
		}
		for _, ext := range rexts {
			l2invs = ext.FilterL2NetworkCascade(l2invs, action)
		}
	}

	msgs := make([]message.NeedReplyMessage, 0, len(l2invs))
	for _, l2inv := range l2invs {
		msg := &hl2.L2NetworkDeletionMsg{
			L2NetworkUuid: l2inv.Uuid,
			DeletionMessage: &message.DeletionMessage{
				NeedReplyMessageImpl: &message.NeedReplyMessageImpl{},
				IsForceDelete:        action.IsActionCode(cascade.DELETION_FORCE_DELETE_CODE),
			},
		}

		ce.bus.MakeTargetServiceIdByResourceUuidMsg(msg, hl2.SERVICE_ID, l2inv.Uuid)

		msgs = append(msgs, msg)
	}

	finalL2invs := l2invs
	ce.bus.SendNeedRepliesWithParallel(msgs, len(msgs), &CloudBusListCallBackHandleDeletion{
		Completion:  completion,
		Action:      action,
		Ce:          ce,
		FinalL2invs: finalL2invs,
	})
}

func (ce *L2NetworkCascadeExtension) HandleDeletionCheck(action *cascade.CascadeAction, completion hcore.Completion) {
	l2invs, _ := ce.l2NetworkFromAction(action)
	if len(l2invs) == 0 {
		completion.Success()
		return
	}

	var err error
	for _, inv := range l2invs {
		if err = ce.extpEmitter.PreDelete(inv); err != nil {
			completion.Fail(herrorcode.ArgErr(err.Error()))
			return
		}
	}
	completion.Success()
}

func (ce *L2NetworkCascadeExtension) GetEdgeNames() []string {
	return []string{
		zone.ModelNameZoneVO,
		identity.ModelNameAccountVO,
	}
}

func (ce *L2NetworkCascadeExtension) GetCascadeResourceName() string {
	return Name
}

func (ce *L2NetworkCascadeExtension) l2NetworkFromAction(action *cascade.CascadeAction) ([]*hl2.L2NetworkInventory, error) {
	var ret []*hl2.L2NetworkInventory
	if action.ParentIssuer == zone.ModelNameZoneVO {
		vs := action.ParentIssuerContext.([]*zone.ZoneInventory)
		zuuids := make([]string, 0, len(vs))
		for _, v := range vs {
			zuuids = append(zuuids, v.Uuid)
		}

		l2vos := make([]*hl2.L2NetworkVO, 0)
		if err := ce.dbf.GetEntityManager().Where("ZoneUuid in ?", zuuids).Find(&l2vos).Error; err != nil {
			return nil, err
		}
		if len(l2vos) != 0 {
			ret = hl2.L2NetworkInventoryValuesOf(l2vos)
		}
	} else if Name == action.ParentIssuer {
		ret = action.ParentIssuerContext.([]*hl2.L2NetworkInventory)
	} else if action.ParentIssuer == identity.ModelNameAccountVO {
		vs := action.ParentIssuerContext.([]*identity.AccountInventory)
		auuids := make([]string, 0, len(vs))
		for _, v := range vs {
			auuids = append(auuids, v.Uuid)
		}

		l2vos := make([]*hl2.L2NetworkVO, 0)
		if err := ce.dbf.GetEntityManager().Exec("select d from L2NetworkVO d, AccountResourceRefVO r where d.uuid = r.resourceUuid and"+
			" r.resourceType = ? and r.accountUuid in (?)", hl2.ModelNameL2NetworkVO, auuids).Scan(&l2vos).Error; err != nil {
			return nil, err
		}

		if len(l2vos) != 0 {
			ret = hl2.L2NetworkInventoryValuesOf(l2vos)
		}
	}

	return ret, nil
}

func (ce *L2NetworkCascadeExtension) CreateActionForChildResource(action *cascade.CascadeAction) *cascade.CascadeAction {
	if misc.IsInStrings(action.ActionCode, cascade.DELETION_CODES) {
		ctx, _ := ce.l2NetworkFromAction(action)
		if ctx != nil {
			n := action.Copy()
			n.ParentIssuer = Name
			n.ParentIssuerContext = ctx

			return n
		}
	} else if action.ActionCode == hl2.DETACH_L2NETWORK_CODE {
		n := action.Copy()
		n.ParentIssuer = Name

		return n
	}

	return nil
}

// --- add
type CloudBusListCallBackHandleDetach struct {
	Completion hcore.Completion
}

func (cb *CloudBusListCallBackHandleDetach) Run(replies []*message.MessageReply) {
	for _, r := range replies {
		if !r.Success {
			cb.Completion.Fail(r.Error)
			return
		}
	}

	cb.Completion.Success()
}

type CloudBusListCallBackHandleDeletion struct {
	Completion  hcore.Completion
	Action      *cascade.CascadeAction
	Ce          *L2NetworkCascadeExtension
	FinalL2invs []*hl2.L2NetworkInventory
}

func (cb *CloudBusListCallBackHandleDeletion) Run(replies []*message.MessageReply) {
	if !cb.Action.IsActionCode(cascade.DELETION_FORCE_DELETE_CODE) {
		for _, r := range replies {
			if !r.Success {
				cb.Completion.Fail(r.Error)
				return
			}
		}
	}

	var uuids []string
	for i := range replies {
		inv := cb.FinalL2invs[i]
		uuids = append(uuids, inv.Uuid)

		log.Glog.Debug(fmt.Sprintf("delete l2 network[uuid:%s, name:%s]", inv.Uuid, inv.Name))
	}

	if err := cb.Ce.dbf.GetEntityManager().Where("Uuuid in (?)", uuids).Delete(hl2.ModelL2NetworkVO).Error; err != nil {
		log.Glog.Warn("CloudBusListCallBackHandleDeletion", zap.Error(err))
	}

	cb.Completion.Success()
}
